import React, { useContext, useLayoutEffect, useState, useCallback } from 'react'

const Context = React.createContext();

export const Provider = function (props) {
    return <Context.Provider value={props.store}>{props.children}</Context.Provider>
}

export function connect(mapStateToProps = state => state, mapDispatchToProps) {
    return (WrappedComponent) => {
        return props => {
            const store = useContext(Context);

            const { getState, dispatch, subscribe } = store;

            const mergeStateToProps = mapStateToProps(getState());

            let mergeDispatchToProps = { dispatch };// mapDispatchToProps 如果不传，默认可以接收dispatch

            if (typeof mapDispatchToProps === 'object') {
                mergeDispatchToProps = bindActionCreators(mapDispatchToProps, dispatch);
            } else {
                mergeDispatchToProps = mapDispatchToProps(dispatch);
            }

            const forceUpdate = useForceUpdate(); // 需要强刷页面
            // 这里比较重要，这里面是做页面的订阅更新，useEffect是异步执行，useLayoutEffect是同步执行，如果异步的时间差里面，做了状态的管理库的变更，那么就不会更新了
            useLayoutEffect(() => {
                // 源码里面是上下级subscription 订阅器进行订阅
                // 首先 state 的改变，Provider 是不能直接下发更新的，如果下发更新，那么这个更新是整个应用层级上的，为了性能考虑，不该更新的组件不能更新
                const unsubscribe = subscribe(() => {
                    forceUpdate();
                });

                return () => {
                    unsubscribe && unsubscribe();
                }
            }, [store])

            return <WrappedComponent
                {...props}
                {...mergeStateToProps}
                {...mergeDispatchToProps}
            ></WrappedComponent>
        }
    }
}

function useForceUpdate() {
    let [ignore, setState] = useState(0);

    const forceUpdate = useCallback(() => {
        setState(x => x + 1);
    }, [])

    return forceUpdate;
}

export function bindActionCreators(creators, dispatch) {
    let obj = {};

    for (let key in creators) {
        obj[key] = (...args) => dispatch(creators[key](...args))
    }

    return obj
}

export function useStore() {
    const store = useContext(Context);
    return store;
}

export function useSelector(selector) {
    const store = useStore();
    const { getState } = store;

    const state = selector(getState())
    const forceUpdate = useForceUpdate();
    useLayoutEffect(() => {
        const unsubscribe = store.subscribe(() => {
            forceUpdate();
        });
        return () => {
            if (unsubscribe) {
                unsubscribe();
            }
        };
    }, [store]);

    return state;
}

export function useDispatch() {
    const store = useStore();
    return store.dispatch;
}